package com.citywithincity.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import com.citywithincity.api.ApiConfig;
import com.citywithincity.db.interfaces.EAr;
import com.citywithincity.db.interfaces.SqlDriver;

public class EArImpl<T> extends SqlBuilder<EAr<T>> implements EAr<T> {
	
	
	private static final UpdateFields EMPTY_UPDATE = new UpdateFields(null,null,null);
	
	
	private Map<String, Object[]> cacheData;
	private Entity<T> entityInfo;

	
	
	public EArImpl(SqlDriver s, Connection source) {
		super(s, source);
		cacheData = new ConcurrentHashMap<String, Object[]>();
	}
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void setInfo(Entity info) {
		this.entityInfo = info;
	}
	@Override
	public T get(Object id) {
		return get(id,true);
	}

	@Override
	public T getOne() {
		return getOne(true);
	}

	@Override
	public T get(Object id, boolean cacheResult) {
		final Entity<T> entityInfo = this.entityInfo;
		where(entityInfo.pk, id);
		return getOne(cacheResult);
	}

	@Override
	public T getOne(boolean cacheResult) {
		T data = getOneNoCache();
		if(cacheResult && data!=null){
			addCachedData(data);
		}
		return data;
	}
	
	
	private void addCachedData(Object data) {
		final Entity<T> entityInfo = this.entityInfo;
		final Map<String, Object[]> cacheData = this.cacheData;
		String key = entityInfo.getUniqueValue(data);
		Object[] result = entityInfo.getValues(data);
		cacheData.put(key, result);
	}

	@Override
	public List<T> limit( int position, int pageSize) {
		select(entityInfo.select);
		buildSelect(entityInfo.tableName,position,pageSize);
		return getList(sql.toString(),true);
	}
	private T getOneNoCache() {
		final Entity<T> entityInfo = this.entityInfo;
		final Connection connection = this.connection;
		select(entityInfo.select);
		buildSelect(entityInfo.tableName);
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			ps = connection.prepareStatement(sql.toString());
			setPs(ps);
			logSql();
			rs = ps.executeQuery();
			if (rs.next()) {
				return entityInfo.fromDb(rs);
			}
			return null;
		} catch (SQLException e) {
			throw new DBException(e);
		} finally {
			clear();
			DbUtil.close(rs, ps);
		}
	}


	@Override
	public List<T> get() {
		final Entity<T> entityInfo = this.entityInfo;
		select(entityInfo.select);
		buildSelect(entityInfo.tableName);
		return getList(sql.toString(), true);
	}
	
	
	protected List<T> getList(String sql, boolean clear) {
		final Entity<T> entityInfo = this.entityInfo;
		final Connection connection = this.connection;
		PreparedStatement ps = null;
		ResultSet rs = null;
		boolean exception = false;
		try {
			ps = connection.prepareStatement(sql);
			setPs(ps);
			rs = ps.executeQuery();
			if(ApiConfig.debug()){
				logSql();
			}
			return entityInfo.getList(rs);
		} catch (SQLException e) {
			logSql();
			exception = true;
			throw new DBException(sql,e);
		}  finally {
			clear(clear || exception);
			DbUtil.close(rs, ps);
		}
	}

	@Override
	public Page<T> position(Integer position, Integer pageSize) {
		final Entity<T> entityInfo = this.entityInfo;
		select(entityInfo.select);
		buildSelect(entityInfo.tableName, position, pageSize);
		Page<T> p = new Page<T>();
		p.setPageSize(pageSize);
		p.setList(getList(sql.toString(), false));
		p.setTotalRows(getRows(entityInfo.tableName));

		p.setPage(driver.posToPage(position, pageSize));
		p.setTotalPages((int) Math.ceil((double) p.getTotalRows() / pageSize));
		return p;
	}

	
	@SuppressWarnings("unchecked")
	@Override
	public int update(Object value, String filter) {
		final Entity<T> entityInfo = this.entityInfo;
		UpdateFields result = getUpdatedFields( (T)value,entityInfo ,filter);
		return update(value, result.getUpdateFields(),result.getValues());
	}
	
	@Override
	public int update(T data, boolean useCache) {
		if (useCache) {
			final Entity<T> entityInfo = this.entityInfo;
			UpdateFields result = getUpdatedFields(data, entityInfo);
			if (result != null) {
				if (result == EMPTY_UPDATE) {
					clear();
					return 0;
				}
				int ret = update(data, result.getUpdateFields(),result.getValues());
				if(ret>0){
					//将更新好的放入内存中
					setUpdateFields(result);
					//这里确认是否一样
					if(ApiConfig.debug()){
						
						Object[] values = entityInfo.getValues(data);
						
						Object[] cachedData = cacheData.get(entityInfo.getUniqueValue(data));
						
						int index = 0;
						for (Object object : cachedData) {
			
							if(object==null ){
								if( values[index]!=null){
									throw new RuntimeException("value is not same");
								}
								
							}else{
							/*	if(!object.equals(values[index])){
									throw new RuntimeException("value is not same");
								}*/
							}
							++index;
							
						}
						
					}
				}
				return ret;
			}
		}
		return updateNoCache(data);
	}
	

	private int updateNoCache(T data) {
		final Entity<T> entityInfo = this.entityInfo;
		final Connection connection = this.connection;
		final StringBuilder sql = this.sql;
		PreparedStatement ps = null;
		try {
			entityInfo.where(data, this);
			EntityField[] entityFields = entityInfo.fields;
			SqlHelper.makeUpdate(sql, entityInfo.tableName, entityFields);
			sql.append(where);
			ps = connection.prepareStatement(sql.toString());
			int index = SqlHelper.setPs(ps, data, entityFields);
			setPs(ps, index);
			return ps.executeUpdate();
		} catch (SQLException e) {
			throw new DBException(e);
		} finally {
			clear();
			DbUtil.close(ps);
		}
	}
	
	private void setUpdateFields(UpdateFields fields) {
		fields.update();
	}
	
	
	public static final class UpdateFields{
		private List<EntityField> updateFields;
		private List<Object> values;
		private Object[] cachedValues;
		public UpdateFields(List<EntityField> updateFields,List<Object> values,Object[] cachedValues){
			this.updateFields = updateFields;
			this.values = values;
			this.cachedValues = cachedValues;
		}
		
		public void update() {
			int index = 0;
			for (EntityField field : updateFields) {
				cachedValues[field.index] = values.get(index);
				++index;
			}
		}

		
		public List<EntityField> getUpdateFields() {
			return updateFields;
		}
		public void setUpdateFields(List<EntityField> updateFields) {
			this.updateFields = updateFields;
		}
		public List<Object> getValues() {
			return values;
		}
		public void setValues(List<Object> values) {
			this.values = values;
		}
		
	}
	
	private UpdateFields getUpdatedFields(T data, Entity<T> entityInfo) {
		String key = entityInfo.getUniqueValue(data);
		Object[] cachedValues = cacheData.get(key);
		if (cachedValues == null) {
			return null;
		}
		// 比较哪些不一样
		int count = entityInfo.fields.length;
		Object[] currentValues = entityInfo.getValues(data);
		List<Object> values = new ArrayList<Object>(count);
		List<EntityField> entityFields = new ArrayList<EntityField>(count);
	
		for (int index = 0; index < count; ++index) {
			Object currentValue = currentValues[index];
			if (currentValue == null) {
				continue;
			}
			Object oldValue = cachedValues[index];
			if (currentValue.equals(oldValue)) {
				continue;
			}
			entityFields.add(entityInfo.fields[index]);
			values.add(currentValue);
		}
		if (entityFields.size() > 0) {
			return new UpdateFields( entityFields, values ,cachedValues);
		} else {
			return EMPTY_UPDATE;
		}

	}
	
	private UpdateFields getUpdatedFields(T data, Entity<T> entityInfo,String filter) {
		// 比较哪些不一样
		int count = entityInfo.fields.length;
		Object[] currentValues = entityInfo.getValues(data);
		EntityField[] fields = entityInfo.getFields();
		List<Object> values = new ArrayList<Object>(count);
		Pattern pattern = Pattern.compile(filter);
		List<EntityField> entityFields = new ArrayList<EntityField>(count);
		int index = 0;
		for (EntityField entityField : fields) {
			if(pattern.matcher(entityField.getField().getName()).matches()){
				entityFields.add(entityField);
				values.add(currentValues[index]);
			}
			++index;
		}
		return new UpdateFields( entityFields, values ,null);

	}

	@Override
	public int update(T data) {
		return update(data, true);
	}

	@Override
	public int insert(T data) {
		try {
			return entityInfo.insert(this.connection, data);
		} catch (SQLException e) {
			throw new DBException(e);
		}finally{
			clear();
		}
	}

	@Override
	public int deleteById(Object id) {
		final Entity<T> entityInfo = this.entityInfo;
		where(entityInfo.pk, id);
		return delete();
	}
	
	@Override
	public int delete() {
		final Entity<T> entityInfo = this.entityInfo;
		final Connection connection = this.connection;
		final StringBuilder sql = this.sql;
		final StringBuilder where = this.where;
		if(where.length() <= 0){
			throw new DBException("Whole table delete is not valid!");
		}
		sql.append("DELETE FROM ").append(entityInfo.tableName).append(where);
		PreparedStatement ps = null;
		try {
			ps = connection.prepareStatement(sql.toString());
			setPs(ps);
			return ps.executeUpdate();
		} catch (SQLException e) {
			throw new DBException(e);
		} finally {
			clear();
			DbUtil.close(ps);
		}
	}
	
	@Override
	public int getRows() {
		return getRows(entityInfo.tableName);
	}

	@Override
	public void releaseConnection() {
		final Map<String, Object[]> cacheData = this.cacheData;
		cacheData.clear();
		super.releaseConnection();
	}
	
	private int update(Object data, List<EntityField> updatedFields, List<Object> updatedValues) {
		final Connection connection = this.connection;
		final StringBuilder sql = this.sql;
		PreparedStatement ps = null;
		try {
			entityInfo.where(data, this);
			SqlHelper.makeUpdate(sql, entityInfo.tableName, updatedFields);
			sql.append(where);
			ps = connection.prepareStatement(sql.toString());
			int index = SqlHelper.setPs(ps, updatedFields, updatedValues);
			setPs(ps, index);
			//替换
			logSql(updatedFields,updatedValues);
			
			return ps.executeUpdate();
		} catch (SQLException e) {
			throw new DBException(e);
		} finally {
			clear();
			DbUtil.close(ps);
		}
	}
	

	
	protected Map<String, Object> toMap(Object entity){
		
		Map<String, Object> data = new HashMap<String, Object>();
		for (EntityField entityField : entityInfo.fields) {
			data.put(entityField.columnName, entityField.get(entity));
		}
		
		return data;
		
	}
	
	@Override
	public int insertOrUpdate(T data) {
		
		//toMap
		String[] keys = new String[entityInfo.getUniqueFields().length];
		int index = 0;
		for (EntityField f : entityInfo.getUniqueFields()) {
			keys[index++] = f.columnName;
		}
		
		return getDriver().insertOrUpdate(this, entityInfo.tableName, keys, toMap(data));
	}
	
	
}
